from collections import defaultdict
import time
from datetime import datetime

from odoo import api, fields, models, _, Command, SUPERUSER_ID
from odoo.exceptions import UserError, ValidationError
from odoo.tools import float_compare, float_round, float_is_zero, format_datetime
from odoo.tools.misc import OrderedSet, format_date, groupby as tools_groupby



class StockMove(models.Model):
    _inherit = 'stock.move'

    history_production_id = fields.Many2one(
        'mrp.production', '拆分单据的移动记录')
    history_raw_material_production_id = fields.Many2one(
        'mrp.production', '拆分单据的移动记录-行')


class MrpProduction(models.Model):
    _inherit = 'mrp.production'

    history_move_raw_ids = fields.One2many(
        'stock.move', 'history_raw_material_production_id', 'Components',
        compute='_compute_history_move_raw_ids', store=True, readonly=False,
        copy=False,
        domain=[('scrapped', '=', False)])
    history_move_finished_ids = fields.One2many(
        'stock.move', 'history_production_id', 'Finished Products', readonly=False,
        compute='_compute_history_move_finished_ids', store=True, copy=False,
        domain=[('scrapped', '=', False)])

    history_stock_move_count = fields.Integer()

    def action_split2(self):
        self.ensure_one()
        if any(production.state not in ('progress', 'confirmed') for production in self):
            raise UserError(_("Only manufacturing orders in either a progress or confirmed state can be split."))
        if self._get_sources():
            raise ValidationError('只允许最外层订单拆分')
        tol_scrap_qty = sum(self.s_scrap_line_ids.mapped('scrap_qty'))

        if tol_scrap_qty >= self.product_qty:
            raise ValidationError(_('The current document has been completely scrapped and cannot be split！'))
        # 确认情况下拆分制造订单
        action = self.env['ir.actions.actions']._for_xml_id('sb_split_mrp.action_mrp_production_split')

        action['context'] = {
            'default_production_id': self.id,
            'new_split': 'new',
            'default_remain_qty': self.panel_qty,
            'default_panel_qty': self.panel_qty,
            'default_value_pnl': self.product_id.value_pnl,
            'default_value_set': self.product_id.value_set,
        }
        return action

    def _split_productions_progress(self, amounts=False):
        def _default_amounts(production):
            return [production.product_qty, production._get_quantity_to_backorder()]

        if not amounts:
            amounts = {}
        # 1 校验拆分数量和总需求数量是否有差异
        mo_amounts = amounts.get(self)
        if not mo_amounts:
            amounts[self] = _default_amounts(self)
        total_amount = sum(mo_amounts)
        diff = float_compare(self.product_qty, total_amount, precision_rounding=self.product_uom_id.rounding)
        if diff:
            # 工单拆分验证数量错误
            raise ValidationError('The ticket split validation quantity is incorrect {}'.format(self.name))
        # amounts[self] = sorted(amounts[self], reverse=True)
        backorder_vals_list = []

        # 2、根据拆分数量创建了新的MO，并将第一行拆分的修改到原单据
        # Create the backorders.
        backorder_vals = {
            'consumption': 'strict',
            'date_start': datetime.now(),
            'mo_type': self.mo_type,
            'picking_type_id': self.picking_type_id.id,
            'location_src_id': self.location_src_id.id,
            'location_dest_id': self.location_dest_id.id,
            'base_mo': self.base_mo,
            'origin': self.origin,
            'company_id': self.company_id.id,
            'customer': self.customer.id,
            'product_id': self.product_id.id,
            'product_uom_id': self.product_uom_id.id,
            's_order_type': self.s_order_type,
            # 's_makeup_order': self.s_makeup_order,
            # 's_makeup_product_id': self.s_makeup_product_id.id
        }
        production = self.with_context(force_date=True)

        # 获取拆分数量,
        backorder_qtys = amounts[production][1:]
        # 拆分的第一行数量，修改到原单据

        for qty_to_backorder in backorder_qtys:
            backorder_vals_list.append(dict(
                backorder_vals,
                product_qty=qty_to_backorder,
                lot_producing_id=False,
                s_sale_order_line_ids=[(4, line.id) for line in production.s_sale_order_line_ids],
                s_development_line_ids=[(4, d.id) for d in production.s_development_line_ids],
                base_mo=production.base_mo
            ))
        backorders = self.env['mrp.production'].create(backorder_vals_list)
        backorders.action_confirm()
        # index = 0
        # production_to_backorders = {}  # {mrp.production(246,): mrp.production(254, 255, 256, 257)}
        # production_ids = OrderedSet()  # OrderedSet([246, 254, 255, 256, 257])
        # for production in self:
        #     production = production.with_context(force_date=True)
        #     number_of_backorder_created = len(amounts.get(production, _default_amounts(production))) - 1
        #     production_backorders = backorders[index:index + number_of_backorder_created]
        #     production_to_backorders[production] = production_backorders
        #     production_ids.update(production.ids)
        #     production_ids.update(production_backorders.ids)
        #     index += number_of_backorder_created

        origin_production_ids = self._get_all_child() + self
        # 源订单，更新工序完成数量
        for production in origin_production_ids:
            if production.state in ('done', 'cancel'):
                continue
            production.with_context(skip_compute_move_raw_ids=True, force_date=True).product_qty = amounts[self][0]
            production.with_context(skip_compute_move_raw_ids=True, force_date=True).qty_producing = amounts[self][0]
            wordorder_ids = production.workorder_ids.filtered(lambda x: x.state == 'done')
            done_line_ids = wordorder_ids.mapped('done_line_ids')
            c_wid = None
            for wl in done_line_ids:
                if wl.workorder_id.id == c_wid:
                    # 已完成明细的默认就只保留一行
                    wl.active = False
                    continue
                qty_production = wl.workorder_id.qty_production
                prev_wo = self.env['mrp.workorder'].sudo().search([
                    ('production_id', '=', wl.workorder_id.production_id.id),
                    ('step', '<=', wl.workorder_id.step)
                ])
                qty_op_reject = sum(prev_wo.mapped('qty_op_reject'))
                qty_op_reject_pnl = sum(prev_wo.mapped('qty_op_reject_pnl'))
                qty_op_reject_set = sum(prev_wo.mapped('qty_op_reject_set'))
                wl.qty_produced = qty_production - qty_op_reject
                wl.qty_produced_pnl = round(qty_production/wl.workorder_id.value_pnl, 2) - qty_op_reject_pnl
                wl.qty_produced_set = round(qty_production/wl.workorder_id.value_set, 2) - qty_op_reject_set
                c_wid = wl.workorder_id.id

            # 更新过数后，没触发wip计算，手动计算一次
            production.workorder_ids.filtered(lambda x: x.state in ('waiting', 'progress', 'ready')).compute_qty_floor()

        all_split_production = self.env['mrp.production']
        for p in backorders:
            all_split_production |= p._get_all_child() + p

        split_production_dict = {}
        for s_p in all_split_production:
            origin_production = origin_production_ids.filtered(lambda x: x.product_id == s_p.product_id).filtered(lambda x: x.state != 'cancel')
            if origin_production in split_production_dict:
                split_production_dict[origin_production] |= s_p
            else:
                split_production_dict[origin_production] = s_p

        self.handle_move_raw_ids(split_production_dict)
        self.handle_workorder(split_production_dict)

        # 4.拆分出来的生产单需要和源单创建时间保持一致，生产单创建时间需要用来统计报表，创建时间为拆分时间影响报表的准确性
        # for pro, bros in production_to_backorders.items():
        #     for b in bros:
        #         b.create_date = pro.create_date

        # self.create_move(total_amount, production_to_backorders)
        # self.create_workorder(backorders)
        # self.update_move_raw_ids(backorders)

        for p_origin, p_ids in split_production_dict.items():
            for p in p_ids:
                p.s_feeding_date = p_origin.s_feeding_date
                p.message_post(body=_("Order splitting, Source order: %s", p_origin.name))
        return all_split_production + origin_production_ids

    def handle_workorder(self, origin_production):
        if not origin_production:
            origin_production = {}

        for p_origin, p_ids in origin_production.items():
            if p_origin.state in ('done', 'cancel'):
                p_ids.with_user(SUPERUSER_ID).action_cancel()
                continue
            for p in p_ids:
                p.with_user(SUPERUSER_ID).workorder_ids.unlink()
                for w in p_origin.workorder_ids:
                    workorder_list = []
                    list_val = {
                        'name': w.name,
                        'product_id': w.product_id.id,
                        'production_id': p.id,
                        'workcenter_id': w.workcenter_id.id,
                        'product_uom_id': p.product_uom_id.id,
                        'operation_id': w.operation_id.id,
                        'qty_production': p.product_qty,
                        'finished_lot_id': w.finished_lot_id.id,
                        'state': 'pending',
                        'date_start': w.date_start,
                        'date_finished': w.date_finished,
                        'duration_expected': w.duration_expected,
                        'is_subcontracting': w.is_subcontracting
                    }
                    if w.state == 'done':
                        list_val['done_line_ids'] = [(0, 0, {
                            'qty_produced': p.product_qty,
                            'user_id': p.env.uid,
                            'qty_produced_pnl': p.panel_qty,
                            'qty_produced_set': round(p.product_qty / w.value_set, 2),
                        })]
                        list_val['state'] = 'done'
                        list_val.update({
                            'state': 'done',
                            'qty_produced': p.product_qty
                        })

                    # 外包-按数量拆分
                    if w.is_subcontracting and w.state != 'done' and (
                            w.subcontracting_received_qty or w.subcontracting_received_pnl_qty):
                        pols_vals = [(4, po.id) for po in w.pol_ids]
                        list_val['pol_ids'] = pols_vals
                        list_val['subcontracting_received_qty'] = p.product_qty
                        list_val['subcontracting_received_pnl_qty'] = p.panel_qty
                        w.subcontracting_received_qty = p.product_qty
                        w.subcontracting_received_pnl_qty = p.panel_qty

                    workorder_list.append(list_val)
                    new_wo = self.env['mrp.workorder'].sudo().create(workorder_list)
                    new_wo.step = w.step
                p.workorder_ids.filtered(lambda x: x.state in ('waiting', 'progress', 'ready')).compute_qty_floor()

            p_ids.compute_attribute()

    def handle_move_raw_ids(self, split_production_dict):
        """
        处理物料消耗
        """
        if not split_production_dict:
            split_production_dict = {}
        for p_origin, p_ids in split_production_dict.items():
            if p_origin.state in ('done', 'cancel'):
                p_ids.with_user(SUPERUSER_ID).action_cancel()
                continue
            p_move_ids = p_ids.mapped('move_raw_ids').filtered(lambda x: x.product_id.categ_id.code and x.product_id.categ_id.code != 'BCP')
            p_move_ids.update({'raw_material_production_id': False})
            p_move_ids.with_user(SUPERUSER_ID).unlink()
            for move in p_origin.move_raw_ids:
                if move.product_id.categ_id.code and move.product_id.categ_id.code == 'BCP':
                    move.product_uom_qty = p_origin.product_uom_qty
                    move.quantity = p_origin.product_uom_qty
                elif move.state not in ('done', 'cancel'):
                    total_production_qty = sum(list(p_ids.mapped('product_uom_qty')) + [p_origin.product_uom_qty])
                    move._do_unreserve()
                    total = move.product_uom_qty
                    rounding = move.product_uom.rounding

                    # 修改源单的用量
                    move.product_uom_qty = float_round(total * (p_origin.product_uom_qty / total_production_qty), precision_rounding=rounding)
                    move.quantity = move.product_uom_qty
                    one_total = float_round(total - move.product_uom_qty, precision_rounding=rounding)
                    for p in p_ids:
                        if p == p_move_ids[-1]:
                            qty = one_total
                        else:
                            qty = float_round(total * (p.product_uom_qty / total_production_qty), precision_rounding=rounding)
                            one_total = float_round(total - qty, precision_rounding=rounding)
                        move.copy({
                            'raw_material_production_id': p.id,
                            'product_uom_qty': qty,
                            'origin': p.name,
                            'reference': p.name,
                            'move_orig_ids': [(4, o.id) for o in move.move_orig_ids],
                            'group_id': False
                        })

    def create_move(self, total_amount, production_to_backorders):
        # 3、创建新的move：move_raw_ids, move_finished_ids
        new_move_vals = []
        for production in self:
            for move in production.move_raw_ids | production.move_finished_ids:
                if move.additional:
                    continue
                # 物料已消耗不需要再复制了
                if move.state == 'done':
                    continue
                # 复制move # 原始单据修改move
                initial_move_vals = move.copy_data(move._get_backorder_move_vals())[0]
                # production_to_backorders： {mrp.production(246,): mrp.production(253,)}
                # 拆分的单据，创建的move
                for bo in production_to_backorders[production]:
                    new_move_val = dict(initial_move_vals, product_uom_qty=bo.product_qty)
                    if move.raw_material_production_id:  # 半成品
                        new_move_val['raw_material_production_id'] = bo.id
                        # 待消耗
                        new_move_val['product_uom_qty'] = bo.product_qty / total_amount * move.product_uom_qty
                        move.product_uom_qty = production.product_qty / total_amount * move.product_uom_qty
                        move.quantity = float_round(production.product_qty / total_amount * move.product_uom_qty, precision_rounding=move.product_uom.rounding)
                    else:
                        # 成品
                        new_move_val['product_uom_qty'] = bo.product_qty
                        new_move_val['production_id'] = bo.id
                        move.product_uom_qty = production.product_qty
                    new_move_vals.append(new_move_val)

        self.env['stock.move'].sudo().create(new_move_vals)

    def create_workorder(self, backorders):
        # 4、重新生成工序信息：工序数据不规范，导致实际作业的序号，和工序序号不一致
        for backorder in backorders:
            backorder.workorder_ids.with_user(SUPERUSER_ID).unlink()

        for backorder in backorders:
            for w in self.workorder_ids:
                workorder_list = []
                list_val = {
                    'name': w.name,
                    'product_id': w.product_id.id,
                    'production_id': backorder.id,
                    'workcenter_id': w.workcenter_id.id,
                    'product_uom_id': backorder.product_uom_id.id,
                    'operation_id': w.operation_id.id,
                    'qty_production': backorder.product_qty,
                    'finished_lot_id': w.finished_lot_id.id,
                    'state': 'pending',
                    'date_start': w.date_start,
                    'duration_expected': w.duration_expected,
                    'is_subcontracting': w.is_subcontracting
                }
                if w.state == 'done':
                    list_val['done_line_ids'] = [(0, 0, {
                        'qty_produced': backorder.product_qty,
                        'user_id': self.env.uid,
                        'qty_produced_pnl': backorder.panel_qty,
                        'qty_produced_set': round(backorder.product_qty/w.value_set, 2),
                    })]
                    list_val['state'] = 'done'

                # 外包-按数量拆分
                if w.is_subcontracting and w.state != 'done' and (w.subcontracting_received_qty or w.subcontracting_received_pnl_qty):
                    pols_vals = [(4, po.id) for po in w.pol_ids]
                    list_val['pol_ids'] = pols_vals
                    list_val['subcontracting_received_qty'] = backorder.product_qty
                    list_val['subcontracting_received_pnl_qty'] = backorder.panel_qty
                    w.subcontracting_received_qty = self.product_qty
                    w.subcontracting_received_pnl_qty = self.panel_qty

                workorder_list.append(list_val)
                new_wo = self.env['mrp.workorder'].sudo().create(workorder_list)
                new_wo.step = w.step

    def update_move_raw_ids(self, backorders):
        production = backorders | self
        all_moves = production.mapped('move_raw_ids')
        for move in all_moves:
            if move.state == 'done':
                continue
            if move.move_line_ids:
                move.move_line_ids.with_user(SUPERUSER_ID).unlink()
            domain = [('available_quantity', '>', 0),
                      ("on_hand", "=", True),
                      ("quantity", ">", 0),
                      ('location_id', '=', move.location_id.id),
                      ('product_id', '=', move.product_id.id)]
            quants = self.env['stock.quant'].sudo().search(domain, order='id')
            quants = quants.filtered(lambda x: x.available_quantity > 0)
            if not quants or move.product_uom_qty > sum([q.available_quantity for q in quants]):
                continue
            move_line_vals = []
            lot_ids = []
            product_uom_qty = move.product_uom_qty
            for quant in quants:
                # 必须要有批次号
                if not quant.lot_id: continue
                if product_uom_qty < quant.available_quantity:
                    move_line_vals.append((0, 0, {
                        'quant_id': quant.id,
                        'location_id': move.location_id.id,
                        'location_dest_id': move.location_dest_id.id,
                        'quantity': product_uom_qty,
                        'product_uom_id': move.product_uom.id,
                        'date': fields.Datetime.now(),
                        'reference': move.name,
                        # 'state': 'draft'
                    }))
                    lot_ids.append(quant.lot_id.id)
                    break
                # 需多个quant 才能满足发货数情况下
                elif quant.available_quantity and product_uom_qty >= quant.available_quantity:
                    move_line_vals.append((0, 0, {
                        'quant_id': quant.id,
                        'location_id': move.location_id.id,
                        'location_dest_id': move.location_dest_id.id,
                        'quantity': quant.available_quantity,
                        'product_uom_id': move.product_uom.id,
                        'date': fields.Datetime.now(),
                        'reference': move.name,
                        # 'state': 'draft'
                    }))
                    lot_ids.append(quant.lot_id.id)
                    product_uom_qty -= quant.available_quantity
            move.move_line_ids = move_line_vals
